package basic;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;

public class CollisionChecker {

	private static final int POINT_NUMBER = 100;
	private static final int NUMBER_ITER = 1000;
	/**
	 * Collision checker constructor
	 * 
	 * @params ?
	 */
	private GridSVG grid = null;
	private Diagram diagram;

	public CollisionChecker(GridSVG grid, Diagram diagram) {
		this.grid = grid;
		this.diagram = diagram;
	}

	/**
	 * Returns points that compose line. Compute slope and constant term accord
	 * to start and end points.
	 * 
	 * @param start
	 * @param end
	 * @return
	 */
	public ArrayList<Point> getPointFromRelation(Point start, Point end) {

		double deltax = end.getX() - start.getX();
		double deltay = end.getY() - start.getY();

		// calcolo il modulo del segmento start-end
		double mod_se = Math.sqrt(Math.pow(deltax, 2) + Math.pow(deltay, 2));

		// calcolo l'angolo compreso tra il segmento start-end e il segmento
		// start-(x,start_y)
		double alfa = 0;
		if (deltax < 0) {
			alfa = (Math.atan((double) (deltay) / (double) (deltax)));
			alfa += Math.PI;
		} else {
			if (deltax == 0) {
				alfa = (deltay < 0 ? +1 : -1) * Math.PI / 2;
			} else
				alfa = (Math.atan((double) (deltay) / (double) (deltax)));
		}

		// divido il modulo per POINT_NUMBER ed ottengo un nuovo modulo
		double mod_first_vett = mod_se / CollisionChecker.POINT_NUMBER;
		// identifico il vettore da utilizzare per trovare sequenzialmente i
		// punti sul segmento
		double first_vett_x = mod_first_vett * Math.cos(alfa);
		double first_vett_y = mod_first_vett * Math.sin(alfa);

		// memorizzo in temp il punto di start
		double temp_x = start.getX();
		double temp_y = start.getY();
		ArrayList<Point> list = new ArrayList<Point>();

		for (int i = 0; i < CollisionChecker.POINT_NUMBER; i++) {
			// aggiungo al punto temp iterativamente il vettore calcolato
			// precedentemente
			temp_x += first_vett_x;
			temp_y += first_vett_y;

			if (deltax == 0 && deltay != 0) {
				if ((int) temp_y != end.getY() && (int) temp_y != start.getY())
					list.add(new Point((int) temp_x, (int) temp_y));

			} else if (deltax != 0 && deltay == 0) {
				if ((int) temp_x != end.getX() && (int) temp_x != start.getX())
					list.add(new Point((int) temp_x, (int) temp_y));
			} else {
				if ((int) temp_x != end.getX() && (int) temp_y != end.getY()
						&& (int) temp_x != start.getX()
						&& (int) temp_y != start.getY())
					list.add(new Point((int) temp_x, (int) temp_y));
			}

		}

		return list;
	}

	/**
	 * Returns entity inside a cell specify by column and row indexes. If there
	 * are no entities null is return.
	 * 
	 * @param indexr
	 * @param indexc
	 * @return
	 */
	public Entity getEntityFromCell(int indexr, int indexc) {
		for (int i = 0; i < this.diagram.getEntities().size(); i++) {
			if (this.diagram.getEntities().get(i).getY() == indexr
					&& this.diagram.getEntities().get(i).getX() == indexc)
				return this.diagram.getEntities().get(i);
		}
		return null;
	}

	/**
	 * Returns cell indexes in which point is placed
	 * 
	 * @param p
	 * @return
	 */
	public int[] getCellFromPoint(Point p) {
		int ncols = this.grid.getCols();
		int nrows = this.grid.getRows();
		int[][] table = new int[nrows][ncols];
		int[] cell = { 0, 0 };

		for (int i = 0; i < table.length; i++) {
			for (int j = 0; j < table[i].length; j++) {
				if (this.isPointInCell(p, i, j)) {
					// riga
					cell[0] = i;
					// colonna
					cell[1] = j;
					return cell;
				}

			}
		}
		return cell;
	}

	/**
	 * Returns true if point passed as argument is placed inside cell, identify
	 * with column and row index, otherwise false.
	 * 
	 * @param p
	 * @param indexr
	 * @param indexc
	 * @return
	 */
	public boolean isPointInCell(Point p, int indexr, int indexc) {
		Point pgrid = this.grid.getCellCoordinates(indexr, indexc);
		int liminfx = pgrid.x - this.grid.getCellWidth(); // cell inferior limit
		// x
		int liminfy = pgrid.y - this.grid.getCellHeigth();// cell inferior limit
		// y
		if (p.x <= pgrid.x && p.x >= liminfx) {
			if (p.y <= pgrid.y && p.y >= liminfy)
				return true;
		}

		return false;
	}

	/**
	 * Returns true if point passed as argument is placed inside entity,
	 * otherwise false.
	 * 
	 * @param ent
	 * @param p
	 * @return
	 */
	public boolean isPointInEntity(Entity ent, Point p) {
		Point end = ent.getEndPoint();
		Point start = ent.getStartPoint();
		if (p.x <= end.x && p.x >= start.x) {
			if (p.y <= end.y && p.y >= start.y)
				return true;
		}
		return false;

	}

	/**
	 * Returns true if given point is placed inside entity identify by cell,
	 * otherwise false
	 * 
	 * @param p
	 * @return
	 */
	public boolean isPointInCollision(Point p) {
		int[] cell = this.getCellFromPoint(p);
		Entity ent = this.getEntityFromCell(cell[0], cell[1]);
		if (ent == null)
			return false;
		return this.isPointInEntity(ent, p);
	}

	/**
	 * Returns true if relation collide with entity otherwise false
	 * 
	 * @param r
	 * @return
	 */
	public boolean isRelationInCollision(Relation r) {
		Point start = r.getStartPoint();
		Point end = r.getEndPoint();
		ArrayList<Point> retpoint = new ArrayList<Point>();
		retpoint.clear();
		retpoint.addAll(this.getPointFromRelation(start, end));

		for (int i = 0; i < retpoint.size(); i++) {
			if (this.isPointInCollision(retpoint.get(i))) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Sets the collision number on CollisionEntity structure
	 * 
	 * @param cent
	 */
	public CollisionEntity setNumberCollision(int id) {
		CollisionEntity cent = new CollisionEntity(this.diagram.getEntities(),
				id);
		cent.setNCollision(getNumberCollisionFromDiagram());
		return cent;
	}

	/**
	 * Returns number of collision by diagram's relations
	 * 
	 * @return
	 */
	public int getNumberCollisionFromDiagram() {
		int coll = 0;
		for (int i = 0; i < this.diagram.getRelations().size(); i++) {
			if (this.isRelationInCollision(this.diagram.getRelations().get(i))) {
				coll++;
			}
		}
		return coll;
	}

	/**
	 * Returns CollisionEntity with minimum number of collision;
	 * 
	 * @return
	 */
	public void getMinCollision(ArrayList<CollisionEntity> sets) {
		int coll = NUMBER_ITER;
		int ide = 0;
		for (int i = 0; i < sets.size(); i++) {
			if (sets.get(i).getNCollision() < coll) {
				coll = sets.get(i).getNCollision();
				ide = sets.get(i).getIdSet();
			}
		}
		for (int i = 0; i < sets.size(); i++) {
			if (sets.get(i).getIdSet() == ide) {
				this.diagram.setEntities(sets.get(i).getEntities());
				// this.diagram.getEntities().addAll(sets.get(i).getEntities());
				break;
			}
		}
	}

	/**
	 * Sets entities random cell' coordinates
	 */
	public void setNewGridCoordinates() {

		this.grid.initialize();
		int rows = 0;
		int cols = 0;
		Random gen = new Random();
		for (int i = 0; i < this.diagram.getEntities().size(); i++) {
			cols = gen.nextInt(this.grid.getCols());
			rows = gen.nextInt(this.grid.getRows());
			while (!this.grid.setNewFreeCellRandom(this.diagram.getEntities()
					.get(i), cols, rows)) {
				cols = gen.nextInt(this.grid.getCols());
				rows = gen.nextInt(this.grid.getRows());
			}
		}
	}

	/**
	 * Returns set of entities positions with minimum number of collisions.
	 */
	public void setBestSet() {
		ArrayList<CollisionEntity> sets = new ArrayList<CollisionEntity>(
				NUMBER_ITER);
		int n_coll = 0;
		for (int i = 0; i < NUMBER_ITER; i++) {
			this.setNewGridCoordinates();
			this.diagram.setEntityCoordinates(this.grid);
			this.diagram.setRelationsCoordinates();
			sets.add(this.setNumberCollision(i));
			n_coll = sets.get(i).getNCollision();
			if (n_coll == 0)
				break;
		}
		this.getMinCollision(sets);
	}
}
